package coroutine

import (
	"fmt"
	"go_learn/base"
	"go_learn/mlog"
	"sync"

	"github.com/panjf2000/ants/v2"
)

type EOperate int

const (
	FreeCo EOperate = iota
	Wait
	YeildCall
	FuncArgs
	Resume
)

type CoroutinePool struct {
	actives sync.Map
	free    []*Coroutine
	main    *Coroutine
	running *Coroutine
	lock    sync.Mutex //互斥锁
}

type Coroutine struct {
	id         uint64
	resumeChan chan *YeldArg
	resumeId   uint64 //唤醒者的id
}

type YeldArg struct {
	OP     EOperate
	Value  []interface{}
	Source uint64
}

func (slf *CoroutinePool) getFree() *Coroutine {
	slf.lock.Lock()
	defer slf.lock.Unlock()
	coLen := len(slf.free)
	if coLen > 0 {
		co := slf.free[coLen-1]
		slf.free = slf.free[:coLen-1]
		return co
	}
	return nil
}

func (slf *CoroutinePool) freeCo(id uint64) {
	value, ok := slf.actives.LoadAndDelete(id)
	co := value.(*Coroutine)
	if !ok {
		return
	}
	slf.lock.Lock()
	defer slf.lock.Unlock()
	slf.free = append(slf.free, co)
	if value, ok := slf.actives.Load(co.resumeId); ok {
		sourceCO := value.(*Coroutine)
		sourceCO.resumeChan <- &YeldArg{OP: FreeCo}
	} else {
		slf.main.resumeChan <- &YeldArg{OP: FreeCo}
	}
}

func (slf *CoroutinePool) coFunc(co *Coroutine, f interface{}) {
	co.id = base.GetGoroutineID()
	defer slf.freeCo(co.id)
	<-co.resumeChan         //调用方需要等待co.id赋值完毕
	args := <-co.resumeChan //激活协程
	slf.running = co
	switch f.(type) {
	case func():
		f.(func())()
	case func(interface{}):
		f.(func(interface{}))(args.Value[0])
	case func(interface{}, error):
		var err error
		if args.Value[1] != nil {
			err = args.Value[1].(error)
		}
		f.(func(interface{}, error))(args.Value[0], err)
	case func([]interface{}):
		f.(func([]interface{}))(args.Value)
	default:
		mlog.Errorf("can't deal specail type ", base.GetFunctionName(f))
	}
}

func (slf *CoroutinePool) Create(process interface{}) uint64 {
	curID := base.GetGoroutineID()
	co := slf.getFree()
	if co == nil {
		co = &Coroutine{0, make(chan *YeldArg), curID}
	}
	ants.Submit(func() {
		slf.coFunc(co, process)
	})
	co.resumeChan <- &YeldArg{OP: Wait} //等待初始化完毕
	slf.actives.Store(co.id, co)
	return co.id
}

func (slf *CoroutinePool) Yeild(OP EOperate, args ...interface{}) *YeldArg {
	curID := base.GetGoroutineID()
	var curCO *Coroutine
	var sourceCO *Coroutine
	if value, ok := slf.actives.Load(curID); ok {
		curCO = value.(*Coroutine)
	} else {
		panic(fmt.Errorf("can't find cur coroutine"))
	}
	if value, ok := slf.actives.Load(curCO.resumeId); ok {
		sourceCO = value.(*Coroutine)
	} else {
		panic(fmt.Errorf("can't find cur coroutine"))
	}
	sourceCO.resumeChan <- &YeldArg{OP: OP, Value: args, Source: curID}
	rst := <-curCO.resumeChan
	curCO.resumeId = rst.Source
	slf.running = curCO
	return rst
}

func (slf *CoroutinePool) Resume(id uint64, args ...interface{}) *YeldArg {
	curID := base.GetGoroutineID()
	var curCO *Coroutine
	var targetCO *Coroutine
	if value, ok := slf.actives.Load(curID); ok {
		curCO = value.(*Coroutine)
	} else {
		panic(fmt.Errorf("can't find cur coroutine %d-main%d", curID, slf.main.id))
	}
	if value, ok := slf.actives.Load(id); ok {
		targetCO = value.(*Coroutine)
	} else {
		panic(fmt.Errorf("can't find cur coroutine"))
	}
	targetCO.resumeChan <- &YeldArg{Resume, args, curID}
	rst := <-curCO.resumeChan
	slf.running = curCO
	return rst
}

func (slf *CoroutinePool) CurCoroutine() (ret *Coroutine, err error) {
	curID := base.GetGoroutineID()
	if value, ok := slf.actives.Load(curID); ok {
		ret = value.(*Coroutine)
	} else {
		err = fmt.Errorf("can't find cur coroutine")
	}
	return
}

func Start() *CoroutinePool {
	co_pool := new(CoroutinePool)
	co_pool.actives = sync.Map{}
	co_pool.free = make([]*Coroutine, 0, 10)
	co := &Coroutine{base.GetGoroutineID(), make(chan *YeldArg), 0}
	co_pool.main = co
	co_pool.actives.Store(co.id, co)
	return co_pool
}
